Skip to content

feat(events): replaces notifications gate with Events API polling#82

Merged
wgordon17 merged 7 commits intogordon-code:mainfrom
wgordon17:feat/events-api-polling
Apr 24, 2026
Merged

feat(events): replaces notifications gate with Events API polling#82
wgordon17 merged 7 commits intogordon-code:mainfrom
wgordon17:feat/events-api-polling

Conversation

@wgordon17
Copy link
Copy Markdown
Member

Summary

  • Replaces broken notifications API gate with 60s Events API polling loop using ETag conditional requests
  • Adds targeted per-repo refreshes with ID-based dedup, surfacedBy union, and hot set seeding
  • Removes notifications OAuth scope, cleans up all notification gate references across code and docs

- Add events.ts with fetchUserEvents(), parseRepoEvents(), ETag management
- Remove notifications gate: hasNotificationChanges, skipped field, background
  tab gating, notification 403 handler, POLL_MANAGED_SOURCES entry
- Add createEventsPollCoordinator with 60s setTimeout chain, dual race guards
- Add fetchTargetedRepoData with MAX_TARGETED_REPOS=10 cap (SEC-IMPL-003)
- Add seedHotSetsFromTargeted for additive hot set population
- Wire targeted merge in DashboardPage with ID-based dedup, surfacedBy union
- Delete poll-notification-effects.test.ts (all tests were notification gate)
- Remove notifications OAuth scope from oauth.ts
- Remove notifications scope UI from LoginPage.tsx
- Replace notifications API source with userEvents in api-usage.ts
- Update DEPLOY.md, README.md, USER_GUIDE.md notification references
- Update test mocks and assertions for the source rename
- Captures cache snapshot eagerly before setTimeout (CR-001/PERF-001)
- Adds per-repo 2-min cooldown to prevent API amplification (PERF-004)
- Adds parts.length guard in fetchTargetedRepoData (SEC-004)
- Wires resetEventsState into resetPollState for test isolation (CR-010)
- Removes stale notification references from README and USER_GUIDE
- Fixes ApiUsageSection test mock for notifications→userEvents rename
STRUCT-012: fetchTargetedRepoData now returns empty data immediately
when cooldown filtering removes all repos, avoiding unnecessary
GraphQL/REST calls and no-op merge pipeline execution.
- events.test.ts: 22 tests for fetchUserEvents (ETag, 304, first-call,
  numeric ID dedup, empty username guard) and parseRepoEvents (filtering,
  flags, case-insensitive matching, timestamps)
- events-poll.test.ts: 22 tests for fetchTargetedRepoData (scoped repos,
  workflow activity filter, cooldown, cap, malformed names),
  seedHotSetsFromTargeted (pending guard, enriched guard, additive only,
  generation preservation), and config-change effects
- Tracked-user item preservation across targeted refresh
- surfacedBy annotation union merging
- detectNewItems + dispatchNotifications called after merge
- seedHotSetsFromTargeted called (not rebuildHotSets)
- MCP relay exclusion (lastRefreshedAt unchanged)
- Extract handleTargetedData to named module-scope function
- Fix fetchUserEvents changed flag on empty first call
- Reset consecutiveFailures on non-error skip paths
- Add isFullRefreshing re-check after fetchTargetedRepoData
- Add user login reactive effect to reset events ETag state
- Remove redundant lowercase Set copy in parseRepoEvents
- Rename trackedRepoNames to getTrackedRepoNames
- Fix indentation in pollFetch if/else block
- Replace plan-tag comments with descriptive WHY comments
- Add EVENTS_POLL_INTERVAL_MS explanatory comment
- Strengthen surfacedBy union tests with value assertions
- Add 13 new tests covering untested code paths
@wgordon17 wgordon17 marked this pull request as ready for review April 24, 2026 18:35
@wgordon17 wgordon17 merged commit 25bbe14 into gordon-code:main Apr 24, 2026
1 check passed
wgordon17 added a commit to wgordon17/github-tracker that referenced this pull request Apr 30, 2026
Merges upstream/main bringing in:
- feat(events): replaces notifications gate with Events API polling (gordon-code#82)
- fix(dashboard): removes stale merged/closed PRs (gordon-code#83)

Conflict in DashboardPage.test.tsx resolved by removing two
skipped-fetch tests (notifications gate concept replaced by events API).

WAF smoke test hardened with retry logic and sequential execution.
wgordon17 added a commit that referenced this pull request Apr 30, 2026
* feat(jira): adds Jira Cloud integration

- Jira OAuth 3LO auth with API token fallback via Worker proxy
- RYO Jira client (~200 LOC): IJiraClient interface, JiraClient (OAuth/Bearer),
  JiraProxyClient (sealed API token through Worker)
- Worker endpoints: token exchange, refresh, API proxy, accessible-resources
- Jira auth store with single-flight refresh, cross-tab sync, triple-guard
- Issue key detection (JIRA_KEY_REGEX) with inline badges on GitHub items
- Jira Assigned tab with JQL search, project grouping, filters, pagination
- Jira issue bookmarking to Tracked tab with absence-based auto-prune
- Settings page: OAuth connect, API token connect, disconnect, key detection toggle
- CSP connect-src updated for api.atlassian.com
- 19 security constraints enforced (sealed tokens, UUID cloudId, endpoint allowlist,
  no-secret logging, generic error responses, separate rate limiter)

* fix(jira): addresses review findings from Phase 4 + 4.5

- fixes Jira tab unreachable from stale custom tab redirect (CR-001)
- adds redirect:error to JiraClient fetch calls (SEC-001/002)
- fixes broken siteUrl in API token mode (QA-001)
- adds null check after ensureJiraTokenValid in getAccessToken (STRUCT-001)
- wires onResealed callback for SEAL_KEY rotation (STRUCT-002/CR-003)
- adds .catch on key detection promise (STRUCT-002-conc)
- clears jira key cache on auth change (STRUCT-003/008)
- extracts jiraStatusCategoryClass to shared util (UI-001/QA-005)
- caps jira key cache at 500 entries (PERF-001)
- returns cache copy not reference (PERF-002)
- adds cf-turnstile-response to CORS Allow-Headers (API-001)
- separates refresh rate limiter from exchange (API-002)
- adds aria-label to tab list (UI-007)
- fixes migration paths for jiraAssigned key (CR-002/QA-002)

* docs(jira): adds Jira integration setup and usage documentation

- USER_GUIDE.md: Jira Cloud Integration section (OAuth, API token, key detection, tabs, bookmarking, troubleshooting)
- CONTRIBUTING.md: Jira env vars for development setup
- README.md: Jira integration feature entry

* fix(jira): resolves all remaining review findings and test gaps

- fixes 9 code quality issues (UI-003/004/006/008/009/012, PERF-004/008, STRUCT-007)
- resolves 10 needs-input findings (API-003/004/005/006/007/008, SEC-003, CR-009, PERF-005, QA-008)
- creates 3 missing test files (jira-keys, JiraBadge, JiraAssignedTab) — 37 new tests
- fixes 17 skipped tests in JiraSection.test.tsx and JiraCallback.test.tsx
- adds JiraAuthStateSchema Zod validation for localStorage parse
- adds payload_too_large ErrorCode, input length caps, bulkfetch item cap
- adds loading spinner, title attributes, type=button, aria headings
- caches HKDF derived key for proxy re-seal performance

* test(jira): adds missing Jira tracked item and tab filter tests

- adds setTabFilter('jiraAssigned') runtime verification tests
- adds Jira tracked item tests: trackItem dedup by jiraKey, untrackJiraItem,
  moveJiraItem, mixed GitHub+Jira coexistence, source migration default
- removes inconsistent VITE_JIRA_CLIENT_ID default from vitest.config.ts

* test(jira): adds TrackedTab Jira component tests and docs

- adds 7 TrackedTab component tests: Jira item rendering with key/title/status,
  htmlUrl linking, mixed GitHub+Jira coexistence, unpin, number-prefix guard
- adds USER_GUIDE troubleshooting entries for API token 403 and multi-tab refresh
- fixes setEnv helper to properly test undefined vs empty string cases

* fix(jira): address 23 PR review findings

- fix: X-Requested-With header changed to 'fetch' in 3 client call sites
- fix: ensureJiraTokenValid guards against missing/invalid expires_in
- security: add validateOrigin to Jira OAuth token exchange and refresh
- security: add email length cap (254 chars) in proxy handler
- security: remove Worker accessible-resources endpoint (access token stays client-side)
- security: add Zod validation for Atlassian accessible-resources response
- perf: lookupKeys returns only requested keys, not full cache copy
- perf: extract pinnedJiraKeys Set memo replacing per-row O(N) scan
- refactor: JiraBadge uses Show accessor form, TypeBadge adds jiraIssue arm
- refactor: use Jira issue.id for tracked item id, unexport JIRA_KEY_REGEX
- style: remove redundant clearJiraKeyCache, fix misleading comments, IIFE
- test: add coverage for onResealed, issueIdsOrKeys cap, SEAL_KEY_NEXT reseal
- test: add jiraStatusCategoryClass done/ghost, pin/unpin, Zod validation

* fix(jira): harden expires_in guard in callback against undefined/NaN

Math.max(undefined, 60) returns NaN — align with auth.ts typeof guard.

* fix(jira): adds Zod validation for token exchange

- Zod schema validates token exchange response
- CORS for POST bulkfetch confirmed working
- Updates jira-keys.ts fallback comment
- Adds test for wrong-shape auth eviction

* fix(jira): address PR review findings from code review cycle

- Guard array spread on proxy reseal to prevent response corruption
- Add per-element string validation for issueIdsOrKeys in proxy
- Add params allowlists for search and issue proxy endpoints
- Unify handleProxySeal to use getJiraEncryptKey (SEAL_KEY_NEXT)
- Add Zod safeParse to cross-tab Jira auth storage sync
- Fix auto-prune to collect keys before mutating store array
- Add jiraAssigned to BUILTIN_TAB_IDS, remove 5 manual carve-outs
- Add key-match verification in JiraProxyClient.getIssue
- Replace per-key evictIfAtCap with batch evictToFit
- Add cloudId to settings export, hoist regex constants
- Fix zod/v4 import, jiraEnabled accessor, dead null check
- Add 8 tests: navigation, expires_in edges, filters, email boundary

* fix(jira): local dev fixes and error observability

- Jira section always visible in Settings (API token works without OAuth client ID)
- Auto-discover Cloud ID from site URL via /api/jira/tenant-info endpoint
- Subdomain + domain picker replaces full URL input (https:// prefix hardcoded)
- Fix siteUrl not stored in config (caused localhost links)
- Fix Undefined priority pill (filter out Jira's Undefined priority name)
- Fix redirect: error unsupported in workerd (8 occurrences across worker)
- Fix Turnstile ready() incompatible with dynamic script loading
- Fix Turnstile size: invisible no longer valid (use compact)
- Fix Turnstile action-mismatch with test keys (skip check when action absent)
- Add Sentry.captureException to 6 silent catch blocks across auth pages and poll
- Surface actual error messages in Jira connect catch block
- Update docs and tests for all changes

* fix(jira): stabilize tab data and add sort/density/collapse

- Move jiraIssues, jiraLoading, jiraKeyMap signals to module level so
  they persist across DashboardPage remounts (e.g., Settings navigation)
- Add onMount Jira fetch to fill data immediately on mount
- Add reactive cleanup effect when Jira auth is cleared
- Add catch-all notification for non-JiraApiError errors
- Clear Jira state in onAuthCleared handler
- Add SortDropdown with priority, status, key, updated options
- Add compact: CSS density variants matching GitHub tabs
- Add per-project expand/collapse via expandedRepos store
- Add project pinning via RepoLockControls with locked-first ordering
- Add updated to DEFAULT_FIELDS for sort-by-updated support
- Default project groups to expanded (collapse on user action)

* fix(jira): address quality gate review findings

- Add isSafeJiraSiteUrl guard for href/htmlUrl (CWE-601)
- Move sort signals to module level for tab-switch persistence
- Fix project toggle using setAllExpanded (toggleExpandedRepo
  was no-op on first click with default-expanded state)
- Remove phantom empty locked groups from pagination
- Use </>  instead of localeCompare for ISO 8601 date sort

* fix(jira): address Layer 2 adversarial review findings

- Add single-flight guard to fetchJiraAssigned (prevents race
  between onMount fetch and deferred poll effect)
- Match isExpanded to codebase pattern (!! coercion, default
  collapsed) so collapse toggle works correctly
- Add updated field to JiraIssueFields type for sort safety
- Update test mock with Proxy for expanded-by-default in tests

* fix(jira): moves fetch guard to module scope

Prevents remount from creating a parallel unguarded fetch
when the previous mount's fetchJiraAssigned is still in-flight.

* fix(jira): adds siteUrl guard to JiraBadge, auto-expands groups

- Guards JiraBadge href with isSafeJiraSiteUrl (same CWE-601 fix
  as JiraAssignedTab)
- Auto-expands all project groups on first render when no prior
  expand/collapse state exists in localStorage

* fix(jira): adds tests for new features, hardens sort lookup

- Uses null-prototype objects for PRIORITY_ORDER and
  STATUS_CATEGORY_ORDER (prevents prototype key collision)
- Adds updated field to test fixtures in both test files
- Adds 8 new tests: priority sort ordering, sort dropdown
  rendering, expand/collapse header + toggle + bulk buttons,
  compact density hides assignee, siteUrl XSS guard

* fix(jira): uses group-aware pagination, natural key sort, test reset

- Replaces flat pagination with groupByRepo + computePageLayout +
  slicePageGroups pipeline (matches IssuesTab/PullRequestsTab pattern,
  groups never split across pages)
- Natural sort for Jira keys: PROJ-9 before PROJ-10 (splits on
  dash, compares project prefix then issue number)
- Exports _resetJiraTabState for test isolation of module-level
  sort signals (called in beforeEach)
- Updates pagination test to use multi-project layout

* fix(jira): backfills siteUrl from auth, normalizes priority names

- Backfills config.jira.siteUrl from jiraAuth on mount when missing
  (fixes localhost links for configs saved before siteUrl field existed)
- Strips parenthesized suffixes from priority names in display badge
  and sort comparator (e.g., 'Low (migrated)' displays as 'Low' and
  sorts correctly with Low priority)

* fix(jira): removes redundant assignee, inlines compact title

- Removes assignee display from Jira Assigned tab (always the
  current user per JQL assignee = currentUser())
- Compact mode: title renders as inline span in the key+badges
  flex row, wrapping naturally instead of forcing a second line
- Comfortable mode: title remains as block <p> below key row

* feat(jira): renders issue type icons with hover tooltips

- Adds issuetype to DEFAULT_FIELDS and JiraIssueFields type
- Renders Atlassian-hosted issue type icon (16x16) before key
- Native title attribute shows type name on hover (e.g., Story,
  Bug, Epic, Task)

* fix(jira): uses Tooltip component for issue type hover

Replaces native title attribute with the project's Tooltip
component, matching the pattern used across all other tabs.

* fix(jira): adds text fallback when issue type icon is missing

Shows type name as ghost badge when iconUrl is absent (e.g.,
migrated or custom issue types without icons).

* chore(jira): adds dev-only diagnostic for missing issuetype

* fix(jira): falls back to text badge when icon blocked by client

Handles ad blockers or content filters that block specific icon
URLs (e.g., epic.svg) via img onerror → text badge fallback.

* feat(jira): adds inline SVG fallback icons for common issue types

Maps Epic (purple lightning), Story (green card), Task (blue check),
Bug (red circle), Subtask to inline SVGs. Falls back to text badge
for unrecognized types. Handles ad-blocked icon URLs gracefully.

* chore(jira): adds dev-only key detection diagnostic

* chore(jira): adds guard-level diagnostic for key detection

* fix(jira): removes defer from key detection effect

The deferred effect never fires when titleFingerprint has already
settled before the effect registers (cached data + identical poll
results). Without defer, the effect runs immediately on mount when
data exists, and the guards handle the no-data case.

* feat(jira): adds source tooltip to JiraBadge (title vs branch)

- JiraBadge accepts optional source prop ('title', 'branch',
  'title & branch') shown in Tooltip on hover
- PullRequestsTab annotates each detected key with its source
  (title, branch, or both)
- IssuesTab passes source='title' (issues have no branch ref)
- Replaces native title attribute with Tooltip component

* fix(jira): improves JiraBadge tooltip with summary and source prefix

Tooltip now shows key+status, issue summary, and 'from: title'
or 'from: branch' on separate lines.

* fix(jira): refines JiraBadge tooltip content

Drops redundant key (already in badge), shows status + summary +
source. Source only shown when differentiating (not for 'title &
branch'). Uses 'discovered from PR title/branch' phrasing.

* fix(jira): adds priority sort type with correct labels

Extends SortOption type with 'priority' variant. Shows
'(highest first)' / '(lowest first)' instead of the nonsensical
'(fewest)' / '(most)' that the generic number type produced.

* feat(jira): improves sort options and moves badges right

Restructures issue row layout to place status and priority badges
on the right side, keeping titles left-aligned. Adds preferredDirection
to SortOption for controlling dropdown list order. Fixes status sort
to use SDLC order (To Do → In Progress → Done). Adds created and
title sort fields. Requests created field from Jira API.

* feat(jira): adds SDLC sub-ordering for indeterminate statuses

Adds STATUS_SDLC_ORDER map derived from Red Hat MGMT project workflows
for fine-grained sorting within the indeterminate status category.
Statuses progress: Assigned → In Progress → Code Review → Dev Complete
→ QA → Approved → Ready for Release → Blocked. Unknown statuses fall
back to alphabetical ordering in the middle of the progression.

* fix(jira): changes default sort to Status (To Do → Done)

* test(jira): temporarily switches JQL to reporter for UAT

* feat(jira): adds scope dropdown for assigned/created/watching

Adds scope filter to Jira tab toolbar with three modes: Assigned to me,
Created by me, and Watching. Scope changes trigger a re-fetch with the
appropriate JQL (assignee/reporter/watcher). Clears issue list on scope
switch to show loading state. Reverts temporary reporter-only JQL.

* fix(jira): pins filter toolbar with sticky positioning

* revert(jira): removes broken sticky toolbar positioning

* fix(jira): address PR review findings — security, correctness, tests

- Align isSafeJiraSiteUrl with server-side ATLASSIAN_HOST_RE regex
- Add validateProxyRequest to all Jira Worker endpoints (token exchange, refresh, tenant-info)
- Replace scope-dependent auto-prune with targeted bulkFetch status check
- Add auth guard to prevent cross-user data race in fetchJiraAssigned
- Reset _jiraFetching in onAuthCleared to prevent permanent fetch blocking
- Move Jira sort state from module-level signals to viewState store
- Apply stripParenthetical normalization to priority filter
- Use filtered count for Jira tab badge (matching other tabs)
- Strip resealed field from JiraProxyClient.searchJql return
- Remove custom domain option from Settings (Jira Cloud uses *.atlassian.net)
- Remove dead Content-Type checks (now handled by validateProxyRequest)
- Extract stripParenthetical to shared format.ts
- Add tests: isSafeJiraSiteUrl, buildJiraAuthorizeUrl, getIssue key-mismatch,
  ensureJiraTokenValid, handleJiraTenantInfo, X-Requested-With validation

* fix(jira): adds auth guard test, explicit array join

- Test cross-user data race: verifies fetchJiraAssigned does not write
  stale issues when auth is cleared mid-flight
- Replace implicit String(array) coercion with explicit Array.isArray
  check and .join(',') in Worker proxy search params

* chore: merges upstream/main into jira branch

Merges upstream/main bringing in:
- feat(events): replaces notifications gate with Events API polling (#82)
- fix(dashboard): removes stale merged/closed PRs (#83)

Conflict in DashboardPage.test.tsx resolved by removing two
skipped-fetch tests (notifications gate concept replaced by events API).

WAF smoke test hardened with retry logic and sequential execution.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant